LIBRARY IEEE;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;

ENTITY cpu IS
	PORT(	clk	: IN STD_LOGIC;
		reset	: IN STD_LOGIC;
		Inport0, Inport1	: IN STD_LOGIC_VECTOR(7 DOWNTO 0);
		LED : OUT STD_LOGIC_VECTOR(7 downto 0);
		Outport0, Outport1, Outport2, Outport3	: OUT STD_LOGIC_VECTOR(15 DOWNTO 0));
END cpu;

ARCHITECTURE a OF cpu IS
	-- Clock Frequency
	constant CPU_FREQ : INTEGER := 50000000;

	------------- Declare the ALU component
	COMPONENT alu IS
		PORT(A, B : IN SIGNED(15 DOWNTO 0);
			F : IN STD_LOGIC_VECTOR(2 DOWNTO 0);
			Y : OUT SIGNED(15 DOWNTO 0);
			N,V,Z : OUT STD_LOGIC);
	END COMPONENT alu;
	
	-------------- Declare the Multiplier
	component multiplier
		port (
		CLOCK: IN std_logic;
		M_A: IN std_logic_VECTOR(15 downto 0);
		M_B: IN std_logic_VECTOR(15 downto 0);
		M_P: OUT std_logic_VECTOR(31 downto 0));
	end component multiplier;

	-------------- Declare signals interfacing to ALU
	SIGNAL ALU_A, ALU_B : SIGNED(15 DOWNTO 0);
	SIGNAL ALU_FUNC : STD_LOGIC_VECTOR(2 DOWNTO 0);
	SIGNAL ALU_OUT : SIGNED(15 DOWNTO 0);
	SIGNAL ALU_N, ALU_V, ALU_Z : STD_LOGIC;
	
	SIGNAL MULTI_A, MULTI_B : std_logic_VECTOR(15 DOWNTO 0);
	SIGNAL MULTI_OUT : std_logic_VECTOR(31 downto 0);
	
	-------------- Declare the 512x16 RAM component
	COMPONENT microram IS
		PORT (  CLOCK   : IN STD_LOGIC ;
			ADDRESS	: IN STD_LOGIC_VECTOR (8 DOWNTO 0);
			DATAOUT : OUT STD_LOGIC_VECTOR (15 DOWNTO 0);
			DATAIN  : IN STD_LOGIC_VECTOR (15 DOWNTO 0);
			WE	: IN STD_LOGIC 
		);
	END COMPONENT;
	
	-------------- Declare the 38x16 RAM component
	COMPONENT sinlookup IS
		PORT (  CLOCK   : IN STD_LOGIC ;
			LUT_ADDRESS	: IN STD_LOGIC_VECTOR (5 DOWNTO 0);
			OUTX : OUT STD_LOGIC_VECTOR (15 DOWNTO 0)
		);
	END COMPONENT;
	
	------------ Declare signals interfacing to RAM
	SIGNAL RAM_DATA_OUT : STD_LOGIC_VECTOR(15 DOWNTO 0);  -- DATAOUT output of RAM
	SIGNAL ADDR : STD_LOGIC_VECTOR(8 DOWNTO 0);	     -- ADDRESS input of RAM
	SIGNAL RAM_WE : STD_LOGIC;
	
	------------ Declare signals interfacing to SIN lookup table
	SIGNAL LUT_DATA_OUT : STD_LOGIC_VECTOR(15 DOWNTO 0);  -- DATAOUT output of LUT
	SIGNAL LUT_ADDR : STD_LOGIC_VECTOR(5 DOWNTO 0);	     -- ADDRESS input of LUT

	------------ Declare the state names and state variable
	TYPE STATE_TYPE IS (Fetch, Operand, Memory, Execute, Hold, Hold2, MultiWait);
	SIGNAL CurrState : STATE_TYPE;
	
	------------ Declare the internal CPU registers
	SIGNAL PC : UNSIGNED(8 DOWNTO 0);
	SIGNAL IR : STD_LOGIC_VECTOR(7 DOWNTO 0);
	SIGNAL MDR : STD_LOGIC_VECTOR(15 DOWNTO 0);
	
	constant TMR_TIME : INTEGER := CPU_FREQ/60;
--	constant TMR_TIME : INTEGER := 31;
	SIGNAL TMR : INTEGER range 0 to TMR_TIME;
	SIGNAL reset_TMR : STD_LOGIC;
	
	constant WDT_TIME : INTEGER := CPU_FREQ*2;
--	constant WDT_TIME : INTEGER := 20;
	SIGNAL WDT : INTEGER range 0 to WDT_TIME;
	SIGNAL WDTreset : STD_LOGIC;
	
	constant DEB_TIME : INTEGER := CPU_FREQ/25;
--	constant DEB_TIME : INTEGER := 10;
	SIGNAL bit0cnt : INTEGER range 0 to DEB_TIME;
	SIGNAL bit1cnt : INTEGER range 0 to DEB_TIME;
	
	SIGNAL A,B : SIGNED(15 DOWNTO 0);
	SIGNAL N,Z,V : STD_LOGIC;
	
	------------ Declare the common data bus
	SIGNAL DATA : STD_LOGIC_VECTOR(15 DOWNTO 0);

	------------ Declare waiting counter for the multiply hardware
	SIGNAL MultiWaitCnt : INTEGER range 0 to 5;
	
	-----------------------------------------------
	-- This function returns TRUE if the given op code
	-- is a 4-phase instruction rather than a 2-phase
	-- instruction
	-----------------------------------------------	
	FUNCTION Is4Phase(CONSTANT DATA : STD_LOGIC_VECTOR(7 DOWNTO 0))
			RETURN BOOLEAN IS
		VARIABLE MSB5 : STD_LOGIC_VECTOR(4 DOWNTO 0);
		VARIABLE MSB7 : STD_LOGIC_VECTOR(6 DOWNTO 0);
		VARIABLE RETVAL : BOOLEAN;
	BEGIN
		MSB5 := DATA(7 DOWNTO 3);
		MSB7 := DATA(7 DOWNTO 1);
		IF (MSB5 = "00000") or (MSB7 = "1000111") THEN
			RETVAL := true;
		ELSE
			RETVAL := false;
		END IF;
		RETURN RETVAL;
	END FUNCTION;
	
	-----------------------------------------------
	-- This function returns TRUE if the given op code
	-- is a branch instruction
	-----------------------------------------------	
	FUNCTION IsBranch(CONSTANT DATA : STD_LOGIC_VECTOR(7 DOWNTO 0))
			RETURN BOOLEAN IS
		VARIABLE MSB7 : STD_LOGIC_VECTOR(6 DOWNTO 0);
		VARIABLE MSB3 : STD_LOGIC_VECTOR(2 DOWNTO 0);
		VARIABLE RETVAL : BOOLEAN;
	BEGIN
		MSB7 := DATA(7 downto 1);
		MSB3 := DATA(7 downto 5);
		IF MSB7 = "0001001" or MSB7 = "0001010" or MSB3 = "011" or MSB7 = "0001110"  or MSB7 = "1001001" or MSB7 = "1001010" THEN
			RETVAL := true;
		ELSE
			RETVAL := false;
		END IF;
		RETURN RETVAL;
	END FUNCTION;

	----------- Declare variables that indicate which
	----------- registers are to be written from the DATA
	----------- bus at the start of the next Fetch cycle.
	SIGNAL Exc_RegWrite, Exc_CCWrite, Exc_IOWrite, Exc_DEB, Exc_Branch, Exc_Skip, Exc_Multi, reset_WDT : STD_LOGIC;
	
BEGIN
	------------------- Instantiate the ALU component
	U1: alu PORT MAP
	(ALU_A, ALU_B, ALU_FUNC, ALU_OUT, ALU_N, ALU_V, ALU_Z);
			
	------------------- Drive the ALU_FUNC input
	ALU_FUNC <= IR(6 DOWNTO 4);
	
	------------------- Instantiate the Multiplier component
	U3 : multiplier
		port map (clk, MULTI_A, MULTI_B, MULTI_OUT);
		
	------------------- Instantiate the RAM component
	U2 : microram
	PORT MAP (CLOCK => clk, ADDRESS => ADDR, DATAOUT => RAM_DATA_OUT, DATAIN => DATA, WE => RAM_WE);

	------------------- Instantiate the LUT component
	U4 : sinlookup
	PORT MAP (CLOCK => clk, LUT_ADDRESS => LUT_ADDR, OUTX => LUT_DATA_OUT);

	------------------ Generate RAM write enable
	-- The address and data are presented to the
	-- RAM during the Memory phase, hence this is
	-- when we need to set RAM_WE high.
	PROCESS (CurrState,IR)
	BEGIN
		IF (CurrState = Memory) AND (IR(7 DOWNTO 2) = "000001") THEN
			RAM_WE <= '1';
		ELSE
			RAM_WE <= '0';
		END IF;
	END PROCESS;
	
	
	------------------------	
	--Watchdog Timer Process
	------------------------
	PROCESS (clk, reset)
	BEGIN
		IF reset = '1' THEN
			WDT <= 0;
		ELSIF rising_edge(clk) THEN
			WDTreset <= '0';
		
			WDT <= WDT + 1;	--increment watchdog timer
		
			IF reset_WDT = '1' THEN
				WDT <= 0;
			END IF;
			
			IF WDT = WDT_TIME THEN
				--uncomment the next line to enable watch dog timer
--				WDTreset <= '1';		
				WDT <= 0;
			END IF;
		END IF;
	END PROCESS;
	
	------------------------	
	--Interrupt Timer Process
	------------------------
	PROCESS (clk, reset, WDTreset)
	BEGIN
		IF reset = '1' or WDTreset = '1' THEN
			TMR <= 0;
		ELSIF rising_edge(clk) THEN		
			TMR <= TMR + 1;	--increment timer
		
			IF reset_TMR = '1' THEN
				TMR <= 0;
			ELSIF TMR = TMR_TIME THEN
				TMR <= TMR;
			END IF;
		END IF;
	END PROCESS;
	
	------------------------
	--Set LED to Instruction
	------------------------
	LED <= IR;
	
	------------------------	
	--Debounce Process
	------------------------
	PROCESS (clk, reset, WDTreset)
	BEGIN
		IF reset = '1' or WDTreset = '1' THEN
			bit0cnt <= 0;
			bit1cnt <= 0;
		ELSIF rising_edge(clk) THEN
			IF bit0cnt < DEB_TIME and Inport0(0) = '0' THEN
				bit0cnt <= bit0cnt + 1;
			ELSIF Inport0(0) = '1' THEN
				bit0cnt <= 0;
			END IF;
		
			IF bit1cnt < DEB_TIME and Inport0(1) = '0' THEN
				bit1cnt <= bit1cnt + 1;
			ELSIF Inport0(1) = '1' THEN
				bit1cnt <= 0;
			END IF;
		END IF;
	END PROCESS;
	
	------------------ Generate address bus
	WITH CurrState SELECT
		ADDR <= STD_LOGIC_VECTOR(PC) WHEN Fetch,
				STD_LOGIC_VECTOR(PC) WHEN Operand, -- really a don't care
				IR(1) & MDR(7 downto 0) WHEN Memory,
				STD_LOGIC_VECTOR(PC) WHEN Execute,
				STD_LOGIC_VECTOR(PC) WHEN OTHERS; -- just to be safe

	------------------ Generate LUT address bus
	WITH CurrState SELECT
		LUT_ADDR <= STD_LOGIC_VECTOR(PC(5 downto 0)) WHEN Fetch,
				"000000" WHEN Operand, -- really a don't care
				"000000" WHEN Memory,
				STD_LOGIC_VECTOR(ram_data_out(5 downto 0)) WHEN Execute,
				"000000" WHEN OTHERS; -- just to be safe
				
	-----------------------------------------------
	-- This is the next-state logic for the 4-phase
	-- state machine.
	-----------------------------------------------
	PROCESS (clk,reset, WDTreset)
	BEGIN
		IF reset = '1' or WDTreset = '1' THEN
			CurrState <= Fetch;
			MultiWaitCnt <= 0;
			PC <= (others => '0');
			IR <= (others => '0');
			MDR <= (others => '0');
			A <= (others => '0');
			B <= (others => '0');
			N <= '0';
			Z <= '0';
			V <= '0';
			Outport0 <= (others => '0');
			Outport1 <= (others => '0');
		ELSIF rising_edge(clk) THEN
			CASE CurrState IS
				WHEN Fetch =>
					IR <= DATA(7 downto 0);
					IF Is4Phase(DATA(7 downto 0)) THEN
						PC <= PC + 1;
						CurrState <= Operand;
					ELSIF IsBranch(DATA(7 downto 0)) THEN
						PC <= PC + 1;
						CurrState <= Execute;
					ELSE
						CurrState <= Execute;
					END IF;

				WHEN Operand =>
					MDR <= DATA;
					CurrState <= Memory;

				WHEN Memory =>
					CurrState <= Execute;
					
				WHEN Execute =>
					PC <= PC + 1;
					CurrState <= Fetch;
					
					IF Exc_RegWrite = '1' THEN
						IF IR(0) = '0' THEN
							A <= SIGNED(DATA);
						ELSE
							B <= SIGNED(DATA);
						END IF;
					END IF;
					
					IF Exc_CCWrite = '1' THEN
						V <= ALU_V;
						N <= ALU_N;
						Z <= ALU_Z;
					END IF;

					IF Exc_IOWrite = '1' THEN
						IF IR(7 DOWNTO 1) = "0001000" THEN	--BCDO
							Outport1 <= "000000000000" & DATA(7 downto 4);
							Outport0 <= "000000000000" & DATA(3 downto 0);
						ELSIF IR(7 downto 2) = "100111" THEN --high ports
							IF IR(1) = '0' THEN
								Outport2 <= DATA;
							ELSE
								Outport3 <= DATA;
							END IF;
						ELSE
							IF IR(1) = '0' THEN
								Outport0 <= DATA;
							ELSE
								Outport1 <= DATA;
							END IF;
						END IF;
					END IF;
					
					IF Exc_Branch = '1' THEN
						PC <= UNSIGNED(IR(0) & DATA(7 downto 0));
						CurrState <= Hold;
					END IF;
					
					IF Exc_Skip = '1' THEN
						CurrState <= Hold;
						IF IR(7 downto 1) = "1000111" THEN	--SINLUT
							PC <= PC;
						END IF;
					END IF;
				
					IF Exc_Multi = '1' THEN
						CurrState <= MultiWait;
						PC <= PC;
						MultiWaitCnt <= 0;
					END IF;
					
				WHEN Hold =>
					PC <= PC + 1;
					IF IR = "00010110" THEN	--skip next instruction
						IF Is4Phase(DATA(7 downto 0)) or IsBranch(DATA(7 downto 0)) THEN
							--PC <= PC + 1;	--skip two bit instruction
							CurrState <= Hold2;
						ELSE
							CurrState <= Fetch;
						END IF;
					ELSIF IR(7 downto 1) = "1000111" THEN	--SINLUT
						IF IR(0) = '0' THEN
							A <= SIGNED(LUT_DATA_OUT);
						ELSE
							B <= SIGNED(LUT_DATA_OUT);
						END IF;
						CurrState <= Fetch;
					ELSE
						CurrState <= Fetch;
					END IF;
				
				WHEN Hold2 =>
					PC <= PC + 1;
					CurrState <= Fetch;
					
				WHEN MultiWait =>
					MultiWaitCnt <= MultiWaitCnt + 1;
					CurrState <= MultiWait;
					IF MultiWaitCnt = 2 THEN
						IF IR(0) = '0' THEN
							A <= SIGNED(MULTI_OUT(31) & MULTI_OUT(19 downto 5));
						ELSE
							B <= SIGNED(MULTI_OUT(31) & MULTI_OUT(19 downto 5));
						END IF;
						CurrState <= Hold2;
					END IF;
					
				WHEN Others =>
					CurrState <= Fetch;
			END CASE;
		END IF;
	END PROCESS;

	PROCESS (CurrState,RAM_DATA_OUT,A,B,ALU_OUT,Inport0,Inport1,IR) 
	BEGIN
		-- Set these to 0 in each phase unless overridden, just so we don't
		-- generate latches (which are unnecessary).
		Exc_RegWrite <= '0';
		Exc_CCWrite <= '0';
		Exc_IOWrite <= '0';
		Exc_Branch <= '0';
		Exc_Skip	<= '0';
		Exc_Multi <= '0';
		reset_WDT <= '0';
		reset_TMR <= '0';
		
		-- Same idea
		ALU_A <= A;
		ALU_B <= B;

		MULTI_A <= STD_LOGIC_VECTOR(A);
		MULTI_B <= STD_LOGIC_VECTOR(B);
		
		-- Same idea
		DATA <= RAM_DATA_OUT;

		CASE CurrState IS
			WHEN Fetch | Operand =>
				DATA <= RAM_DATA_OUT;
						
			WHEN Memory =>
				IF IR(0) = '0' THEN
					DATA <= STD_LOGIC_VECTOR(A);
				ELSE
					DATA <= STD_LOGIC_VECTOR(B);
				END IF;
			
			WHEN Hold =>
				null;
				
			WHEN Hold2 =>
				null;
			
			WHEN MultiWait =>
				null;
				
			WHEN Execute =>
				CASE IR(7 DOWNTO 1) IS
					WHEN   "1000000" 			-- ADD R
						| "1001000"			-- SUB R
						| "1100000"			-- XOR R
						| "1111000" =>			-- CLR R
						DATA <= STD_LOGIC_VECTOR(ALU_OUT);
						Exc_RegWrite <= '1';
						Exc_CCWrite <= '1';
						
					WHEN   "1010000"				-- LSL R
						 | "1011000"				-- LSR R
						 | "1101000"				-- COM R
						 | "1110000" =>				-- NEG R
						 IF IR(0) = '0' THEN
						 	ALU_A <= A;
						 ELSE
						 	ALU_A <= B;
						 END IF;
						 DATA <= STD_LOGIC_VECTOR(ALU_OUT);
						 Exc_RegWrite <= '1';
						 Exc_CCWrite <= '1';

					WHEN "0000100"|"0000101"	-- OUT R,P
							|"0001000" | "1001110" | "1001111" => 			--BCDO R
						IF IR(0) = '0' THEN
							DATA <= STD_LOGIC_VECTOR(A);
						ELSE
							DATA <= STD_LOGIC_VECTOR(B);
						END IF;
						Exc_IOWrite <= '1';
				
					WHEN "0000110"|"0000111" =>	-- IN P,R
						IF IR(1) = '0' THEN
							DATA <= "00000000"&Inport0;
						ELSE
							DATA <= "00000000"&Inport1;
						END IF;
						Exc_RegWrite <= '1';
						
					WHEN "0000000"|"0000001" => -- LOAD M,R
						DATA <= RAM_DATA_OUT;
						Exc_RegWrite <= '1';
						
					WHEN "0000010"|"0000011" =>	-- STOR R,M
						null;

					WHEN "1000111" =>	-- SINLUT
						DATA <= LUT_DATA_OUT;
						Exc_Skip <= '1';
						
					WHEN "0001100" | "0001101" =>	--DEB #,R
						IF IR(1) = '0' AND bit0cnt >= DEB_TIME THEN
							DATA <= "0000000000000001";
						ELSIF IR(1) = '0' AND bit0cnt < DEB_TIME THEN
							DATA <= "0000000000000000";
						END IF;
						
						IF IR(1) = '1' AND bit1cnt >= DEB_TIME THEN
							DATA <= "0000000000000001";
						ELSIF IR(1) = '1' AND bit1cnt < DEB_TIME THEN
							DATA <= "0000000000000000";
						END IF;
						
						Exc_RegWrite <= '1';
					
					WHEN "0001110" =>	--JMP M		
						Exc_Branch <= '1';
					
					WHEN "1001010" =>	--BN M		
						IF N = '1' THEN
							Exc_Branch <= '1';
						END IF;
					
					WHEN "0001001" =>	--BNZ M		
						IF Z = '0' THEN
							Exc_Branch <= '1';
						END IF;
					
					WHEN "0001010" =>	--BZ M		
						IF Z = '1' THEN
							Exc_Branch <= '1';
						END IF;
					
					WHEN	"0110000" |	"0110001" | "0110010" |-- BRSET
							"0110011" | "0110100" | "0110101" |
							"0110110" | "0110111" | "0111000" |
							"0111001" | "0111010" | "0111011" |
							"0111100" | "0111101" | "0111110" | "0111111" =>
							
						IF IR(4) = '0' THEN	--register A
							IF A(CONV_INTEGER(UNSIGNED(IR(3 downto 1)))) = '1' THEN
								Exc_Branch <= '1';
							END IF;
						ELSE	--register B
							IF B(CONV_INTEGER(UNSIGNED(IR(3 downto 1)))) = '1' THEN
								Exc_Branch <= '1';
							END IF;
						END IF;
						
					WHEN "0001011" =>
						IF IR(0) = '0' THEN
							IF A = B THEN	--CPSE
								Exc_Skip <= '1';
							END IF;
						ELSE --CLRWDT
							reset_WDT <= '1';
						END IF;
						
					WHEN "0001111" =>	--MULT R
						Exc_Multi <= '1';
					
					WHEN "1001001" =>	-- CHKTMR
						IF TMR = TMR_TIME THEN
							reset_TMR <= '1';
							Exc_Branch <= '1';
						END IF;
					
					WHEN OTHERS =>
						null;
				END CASE;
		END CASE;	
	END PROCESS;
END a;

